package miner

import (
	"encoding/json"
	"io/ioutil"
	"miners-monitor/models/common"
	"net/http"

	"github.com/filecoin-project/go-address"
	"github.com/milkbobo/fishgoweb/language"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"context"
)

type MinerAoModel struct {
	common.BaseModel
	CommonFunc common.CommonFunc
	MinerDb    MinerDbModel
}

var minerIds []string
var minerAddress map[string]address.Address

func (this *MinerAoModel) Search(filter bson.D) MinerBases {
	opts := this.CommonFunc.GetPaging()
	result := MinerBases{}
	result.Data = this.Find(filter, opts)
	result.Total = this.MinerDb.CountDocuments(filter, options.Count())
	return result
}

func (this *MinerAoModel) FindByStruct(filter MinerBase, opts *options.FindOptions) []MinerBase {
	filterBsonD, err := this.CommonFunc.StructToBsonD(filter)
	if err != nil {
		panic(err)
	}
	mongoResult := this.MinerDb.Find(filterBsonD, opts)
	result := []MinerBase{}
	err = mongoResult.All(context.TODO(), &result)
	if err == mongo.ErrNoDocuments {
		return []MinerBase{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *MinerAoModel) Find(filter bson.D, opts *options.FindOptions) []MinerBase {
	mongoResult := this.MinerDb.Find(filter, opts)
	result := []MinerBase{}
	err := mongoResult.All(context.TODO(), &result)
	if err == mongo.ErrNoDocuments {
		return []MinerBase{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *MinerAoModel) FindOne(filter MinerBase, opts *options.FindOneOptions) MinerBase {
	mongoResult := this.MinerDb.FindOne(filter, opts)
	result := MinerBase{}
	err := mongoResult.Decode(&result)
	if err == mongo.ErrNoDocuments {
		return MinerBase{}
	} else if err != nil {
		panic(err)
	}
	return result
}

func (this *MinerAoModel) GetMinerIds() []string {
	if len(minerIds) > 0 {
		return minerIds
	}
	this.RefreshMinerIds()
	result := []string{}
	for _, singleMinerId := range minerIds {
		result = append(result, singleMinerId)
	}
	return result
}

func (this *MinerAoModel) GetMinerIdsOutMap() map[string]MinerBase {
	mongoResult := this.MinerDb.Find(bson.D{}, options.Find())
	allData := []MinerBase{}
	err := mongoResult.All(context.TODO(), &allData)
	if err == mongo.ErrNoDocuments {
		return map[string]MinerBase{}
	} else if err != nil {
		panic(err)
	}
	result := map[string]MinerBase{}
	for _, singleMiner := range allData {
		result[singleMiner.MinerId] = singleMiner
	}
	return result
}

func (this *MinerAoModel) GetMinerAddress() map[string]address.Address {
	if len(minerAddress) > 0 {
		result := make(map[string]address.Address)
		for k, v := range minerAddress {
			result[k] = v
		}
		return result
	}
	this.RefreshMinerIds()
	result := make(map[string]address.Address)
	for k, v := range minerAddress {
		result[k] = v
	}
	return result
}

func (this *MinerAoModel) RefreshMinerIds() {
	mongoResult := this.MinerDb.Find(bson.D{}, options.Find())
	allData := []MinerBase{}
	err := mongoResult.All(context.TODO(), &allData)
	if err == mongo.ErrNoDocuments {
		return
	} else if err != nil {
		panic(err)
	}
	result := []string{}
	for _, singleMiner := range allData {
		result = append(result, singleMiner.MinerId)
	}
	minerIds = result
	minerAddress = map[string]address.Address{}
	for _, singleMinderId := range minerIds {
		addr, err := address.NewFromString(singleMinderId)
		if err != nil {
			panic(err)
		}
		minerAddress[singleMinderId] = addr
	}
}

func (this *MinerAoModel) Upsert(update MinerBase) {
	miner := this.FindOne(MinerBase{MinerId: update.MinerId},options.FindOne())
	if miner.CreateTimestamp == 0 {
		update.CreateTimestamp = this.GetCreateTimestamp(update.MinerId)
	}
	this.MinerDb.Upsert(update)
	this.RefreshMinerIds()
}

func (this *MinerAoModel) GetCreateTimestamp(minerId string) int64 {
	resp, err := http.Get("https://filfox.info/api/v1/address/" + minerId)
	if err != nil {
		language.Throw(1, err.Error())
	}
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		language.Throw(1, err.Error())
	}
	result := struct {
		CreateTimestamp int64 `json:"createTimestamp"`
	}{}
	err = json.Unmarshal(bytes, &result)
	if err != nil {
		language.Throw(1, err.Error())
	}
	return result.CreateTimestamp
}

func (this *MinerAoModel) DeleteOne(update MinerBase) {
	this.MinerDb.DeleteOne(update)
	this.RefreshMinerIds()
}

func (this *MinerAoModel) AddWorkerKey(minerId string, addr address.Address) {
	singleMiner := this.FindOne(MinerBase{MinerId: minerId}, options.FindOne())
	if singleMiner.MinerId == "" {
		return
	}
	addrString := addr.String()
	if addrString == "<empty>" {
		return
	}
	for _, singleWorkerKey := range singleMiner.RoundWinnerWorkerKey {
		if singleWorkerKey == addrString {
			return
		}
	}
	singleMiner.RoundWinnerWorkerKey = append(singleMiner.RoundWinnerWorkerKey, addrString)
	this.Upsert(MinerBase{
		MinerId: minerId,
		RoundWinnerWorkerKey: singleMiner.RoundWinnerWorkerKey,
	})
}
